package com.cursino.wilson.game.baccarat;

import com.cursino.wilson.game.card.Card;
import com.cursino.wilson.game.card.Hand;
import com.cursino.wilson.game.card.Shoedeck;

/**
 * Game class, which represents a Baccarat game. It will have two main methods, one for playing the game and another to retrieve the statistics. A third method
 * to print results will be also available.
 * @author <a href="mailto:"wilson.cursino@gmail.com"> Wilson Soethe Cursino </a>
 * @since 1.0
 */
public class Baccarat {
    private Shoedeck shoedeck;
    private final static int DEFAULT_DECKS_TO_USE = 8;
    private final static int MINIMUM_CARDS_IN_DECK = 6;

    /**
     * Default constructor.
     */
    public Baccarat() {
        shoedeck = new Shoedeck(DEFAULT_DECKS_TO_USE);
        shoedeck.shuffle();
    }

    /**
     * Statistics for the current card shoedeck deck.
     * @return the string that represents the statistics for the current shoedeck.
     */
    public BaccaratStats statistics() {
        BaccaratStats baccaratStats = new BaccaratStats();

        // how big is our shoedeck right now?
        int shoeSize = shoedeck.getShoe().size();

        // Sanity check for the deck size.
        if (shoeSize < MINIMUM_CARDS_IN_DECK) {
            return baccaratStats;
        }
        // get the actual quantity for each card value in the shoedeck. (10 and faces are all together)
        int[] cards = shoedeck.getQtyInShoeByValue();

        // Winner Combinations
        double playerWinnerCombinations = 0;
        double bankerWinnerCombinations = 0;
        double tieCombinations = 0;

        // 10 different types of value and we need to combine for each card dealt.
        // We will combine each hand with the possibilities and having the winner combination.
        for (int playerCardOneCount = 0; playerCardOneCount < 10; playerCardOneCount++) { // player card1

            // winner combination will be all the cards for that particular value. This is to be used agains the banker card 1.
            double playerCardOneWinnerCombination = cards[playerCardOneCount]--;

            // decrease the shoedeck
            shoeSize--;

            for (int bankerCardOneCount = 0; bankerCardOneCount < 10; bankerCardOneCount++) { // banker card 1

                // winner combination will be the banker card quantity in the shoedeck multiply by the winner combinations for the first card in the players
                // hand.
                double bankerCardOneWinnerCombination = playerCardOneWinnerCombination * cards[bankerCardOneCount]--;

                // again, decrease the shoedeck.
                shoeSize--;

                for (int playerCardTwoCount = 0; playerCardTwoCount < 10; playerCardTwoCount++) { // player card 2

                    // compute combination for player card two.
                    double playerCardTwoWinnerCombination = bankerCardOneWinnerCombination * cards[playerCardTwoCount]--;
                    // decrease the shoedeck
                    shoeSize--;

                    for (int bankerCardTwoCount = 0; bankerCardTwoCount < 10; bankerCardTwoCount++) {// banker card 2
                        // combination...
                        double bankerCardTwoWinnerCombination = playerCardTwoWinnerCombination * cards[bankerCardTwoCount]--;
                        // decrease the shoedeck
                        shoeSize--;

                        // Now start calculating...
                        int playerTotalPoints = (playerCardOneCount + playerCardTwoCount) % 10; // points so far for player
                        int bankerTotalPoints = (bankerCardOneCount + bankerCardTwoCount) % 10; // points so far for banker

                        // going through the rules now
                        if (((playerTotalPoints >= 8) || (bankerTotalPoints >= 8)) || ((playerTotalPoints >= 6) && (bankerTotalPoints >= 6))) { // no extra
                                                                                                                                                // card,
                            // player and banker
                            // stand.
                            if (bankerTotalPoints > playerTotalPoints) {
                                bankerWinnerCombinations += bankerCardTwoWinnerCombination * shoeSize * (shoeSize - 1);
                            } else if (playerTotalPoints > bankerTotalPoints) {
                                playerWinnerCombinations += bankerCardTwoWinnerCombination * shoeSize * (shoeSize - 1);
                            } else {
                                tieCombinations += bankerCardTwoWinnerCombination * shoeSize * (shoeSize - 1);
                            }
                        } else if (playerTotalPoints < 6) {
                            // player gets extra card, banker might get another.

                            for (int playerCardThreeCount = 0; playerCardThreeCount < 10; playerCardThreeCount++) {// for each possible third card for player.
                                double playerCardThreeWinnerCombination = bankerCardTwoWinnerCombination * cards[playerCardThreeCount]--;
                                shoeSize--;
                                int playerTotalPointsWithThirdCard = (playerCardOneCount + playerCardTwoCount + playerCardThreeCount) % 10;
                                boolean bankerGetsExtraCard = true;

                                if (bankerTotalPoints == 7) {
                                    bankerGetsExtraCard = false;
                                } else if ((bankerTotalPoints == 6) && ((playerCardThreeCount < 6) || (playerCardThreeCount > 7))) {
                                    bankerGetsExtraCard = false;
                                } else if ((bankerTotalPoints == 5) && ((playerCardThreeCount < 4) || (playerCardThreeCount > 7))) {
                                    bankerGetsExtraCard = false;
                                } else if ((bankerTotalPoints == 4) && ((playerCardThreeCount < 2) || (playerCardThreeCount > 7))) {
                                    bankerGetsExtraCard = false;
                                } else if ((bankerTotalPoints == 3) && (playerCardThreeCount == 8)) {
                                    bankerGetsExtraCard = false;
                                }

                                if (bankerGetsExtraCard) {
                                    for (int bankerCardThreeCount = 0; bankerCardThreeCount < 10; bankerCardThreeCount++) {// for each possible third card for
                                                                                                                           // banker.
                                        double bankerCardThreeWinnerCombination = playerCardThreeWinnerCombination * cards[bankerCardThreeCount]--;
                                        int bankerTotalPointsWithThirdCard = (bankerCardOneCount + bankerCardTwoCount + bankerCardThreeCount) % 10;

                                        if (bankerTotalPointsWithThirdCard > playerTotalPointsWithThirdCard) {
                                            bankerWinnerCombinations += bankerCardThreeWinnerCombination;
                                        } else if (playerTotalPointsWithThirdCard > bankerTotalPointsWithThirdCard) {
                                            playerWinnerCombinations += bankerCardThreeWinnerCombination;
                                        } else {
                                            tieCombinations += bankerCardThreeWinnerCombination;
                                        }

                                        cards[bankerCardThreeCount]++;

                                    }
                                } else {
                                    if (bankerTotalPoints > playerTotalPointsWithThirdCard) {
                                        bankerWinnerCombinations += playerCardThreeWinnerCombination * shoeSize;
                                    } else if (playerTotalPointsWithThirdCard > bankerTotalPoints) {
                                        playerWinnerCombinations += playerCardThreeWinnerCombination * shoeSize;
                                    } else {
                                        tieCombinations += playerCardThreeWinnerCombination * shoeSize;
                                    }

                                }

                                cards[playerCardThreeCount]++;// just restore value for future loop usage.
                                shoeSize++;// restore shoedeck size.
                            }
                        } else if (bankerTotalPoints < 6) {
                            // player stands, banker hits

                            for (int bankerCardThreeCount = 0; bankerCardThreeCount < 10; bankerCardThreeCount++) {// for each possible third card for banker.
                                double bankerCardThreeWinnerCombination = bankerCardTwoWinnerCombination * cards[bankerCardThreeCount]--;
                                shoeSize--;
                                int bankerTotalPointsWithThirdCard = (bankerCardOneCount + bankerCardTwoCount + bankerCardThreeCount) % 10;

                                if (bankerTotalPointsWithThirdCard > playerTotalPoints) {
                                    bankerWinnerCombinations += bankerCardThreeWinnerCombination * shoeSize;
                                } else if (playerTotalPoints > bankerTotalPointsWithThirdCard) {
                                    playerWinnerCombinations += bankerCardThreeWinnerCombination * shoeSize;
                                } else {
                                    tieCombinations += bankerCardThreeWinnerCombination * shoeSize;
                                }
                                cards[bankerCardThreeCount]++;
                                shoeSize++;
                            }
                        }
                        cards[bankerCardTwoCount]++;
                        shoeSize++;
                    }
                    cards[playerCardTwoCount]++;
                    shoeSize++;
                }
                cards[bankerCardOneCount]++;
                shoeSize++;
            }
            cards[playerCardOneCount]++;
            shoeSize++;
        }

        // simple total calculation.
        double totalCombinations = bankerWinnerCombinations + playerWinnerCombinations + tieCombinations;
        baccaratStats.setTotalCombinations(totalCombinations);

        // probability = # of desirable outcomes / # of possible outcomes.
        double bankerWinnerProbability = bankerWinnerCombinations / totalCombinations;
        double playerWinnnerProbability = playerWinnerCombinations / totalCombinations;
        double tieProbability = tieCombinations / totalCombinations;

        // odds = # of desirable outcomes/ # of undesirable outcomes.
        double bankerOdds = bankerWinnerCombinations / (playerWinnerCombinations + tieCombinations);
        double playerOdds = playerWinnerCombinations / (bankerWinnerCombinations + tieCombinations);
        double tieOdds = tieCombinations / (playerWinnerCombinations + bankerWinnerCombinations);

        baccaratStats.setBankerWinnerProbability(bankerWinnerProbability);
        baccaratStats.setPlayerWinnnerProbability(playerWinnnerProbability);
        baccaratStats.setTieProbability(tieProbability);
        baccaratStats.setBankerOdds(bankerOdds);
        baccaratStats.setPlayerOdds(playerOdds);
        baccaratStats.setTieOdds(tieOdds);

        return baccaratStats;
    }

    /**
     * This method will play once and will remove the cards from the shoedeck.
     * @return simple game result (TIE, BANKER or PLAYER)
     */
    public BaccaratResult playOnce() {
        BaccaratResult result = new BaccaratResult();
        if (shoedeck.getShoe().size() < MINIMUM_CARDS_IN_DECK) {
            result.setGameResult(GameResult.FINAL_GAME);
            return result;
        }

        // create a game, banker and player get two cards.
        Hand playerHand = new Hand();
        Hand bankerHand = new Hand();
        playerHand.addCard(shoedeck.popCard());
        bankerHand.addCard(shoedeck.popCard());
        playerHand.addCard(shoedeck.popCard());
        bankerHand.addCard(shoedeck.popCard());

        // rules
        int playerThirdCardValue = -1;

        // Check if the player needs another card
        if (BaccaratRules.doesPlayerGetsAnotherCard(bankerHand.points(), playerHand.points())) {
            Card thirdCard = shoedeck.popCard();
            playerHand.addCard(thirdCard);
            playerThirdCardValue = thirdCard.getValue().getValue();
        }

        // check the banker to see if needs another card
        if (BaccaratRules.doesBankerGetsAnotherCard(bankerHand.points(), playerHand.points(), playerThirdCardValue)) {
            bankerHand.addCard(shoedeck.popCard());
        }
        GameResult gameResult;
        if (bankerHand.points() - playerHand.points() == 0) {
            gameResult = GameResult.TIE;
        } else if (bankerHand.points() - playerHand.points() > 0) {
            gameResult = GameResult.BANKER;
        } else {
            gameResult = GameResult.PLAYER;
        }
        result.setGameResult(gameResult);
        result.setBankerHand(bankerHand);
        result.setPlayerHand(playerHand);
        return result;
    }
}
